/* Doug working on file 3/3/12 1:00 pm */

/*
 * XMLFacade.java
 */
 
package file;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import exceptions.UPException;
import model.Job;
import model.Park;
import model.PhoneNumber;
import model.PostalAddress;
import model.Role;
import model.User;

/**
 * XMLFacade class
 * 
 * @author Doug Hutchens
 * @version 1.0
 */


public class XMLFacade {
	
	/**
	 * Users XML file to read from.
	 */	
		
	private static final String USER_FILE = "users.xml";
	
	/**
	 * Park XML file to read from.
	 */
	
	private static final String PARK_FILE = "parks.xml";
	
	/**
	 * Jobs XML file to read from.
	 */
	
	private static final String JOB_FILE = "jobs.xml";
	
	/**
	 * Configs XML file to read from.
	 */
	
	private static final String CONFIG_FILE = "configs.xml";
	
	/**
	 * Job node name.
	 */
	
	private static final String JOB_NODE = "Job";
	
	/**
	 * User node name.
	 */
	
	private static final String USER_NODE = "User";
	
	/**
	 * Park node name.
	 */
	
	private static final String PARK_NODE = "Park";
	
	
	/**
	 * UserList node name.
	 */
	
	private static final String USER_LIST = "UserList";
	
	/**
	 * JobList node name.
	 */
	
	private static final String JOB_LIST = "JobList";
	
	/**
	 * ParkList node name.
	 */
	
	private static final String PARK_LIST = "ParkList";
	
	/**
	 * ConfigList node name.
	 */
	
	private static final String CONFIG_LIST = "ConfigList";
	
	/**
	 * Private instance of DocumentBuilderFactory.
	 */
	
	private DocumentBuilderFactory my_factory;
	
	/**
	 * Private instance of users xml file.
	 */
	
	private String my_users_xml;
	
	/**
	 * Private instance of jobs xml file.
	 */
	
	private String my_jobs_xml;
	
	/**
	 * Private instance of parks xml file.
	 */
	
	private String my_parks_xml;
		
	/**
	 *  Default Constructor. 
	 *  
	 */
	
	public XMLFacade() {
		
		my_factory = DocumentBuilderFactory.newInstance();
		my_users_xml = USER_FILE;
		my_jobs_xml = JOB_FILE;
		my_parks_xml = PARK_FILE;
	}
	
	/**
	 * Overloaded Constructor allows passing xml files to XMLFacade.
	 * 
	 * @assumes all parameters are non-null.
	 * @param the_users_file that is specified.
	 * @param the_jobs_file that is specified.
	 * @param the_parks_file that is specified.
	 */
	
	public XMLFacade(final String the_users_file,
			         final String the_jobs_file,
			         final String the_parks_file) {
		
		my_factory = DocumentBuilderFactory.newInstance();		
		my_users_xml = the_users_file;
		my_jobs_xml = the_jobs_file;
		my_parks_xml = the_parks_file;		
		
	}
	
		
	/**
	 * loadUsers() - loads the users from an
	 * XML file for Urban Parks application.
	 * 
	 * 
	 * @return Collection<User> that represents
	 * the list of users in the application.
	 * @throws UPException 
	 */
	
	public Collection<User> loadUsers() throws UPException {
		
		Collection<User> result = new ArrayList<User>();
		
		Document document = getDocument(my_users_xml);
		NodeList nodes = getNodeList(document, USER_LIST);
		
		for(int i = 0; i < nodes.getLength(); i++) {
			Node node = nodes.item(i);
			
			if (node.hasAttributes()) {
				result.add(getUser(node));
			}			
		}	
		
		return result;
	}
	
	/**
	 * loadJobs() - loads the jobs from an
	 * XML file for Urban Parks application.
	 * 
	 * 
	 * @return Collection<Job> that represents
	 * the list of jobs in the application.
	 * @throws UPException 
	 */
	
	public Collection<Job> loadJobs() throws UPException {
		
		Collection<Job> result = new ArrayList<Job>();
		
		Document document = getDocument(my_jobs_xml);
		NodeList nodes = getNodeList(document, JOB_LIST);
		
		for(int i = 0; i < nodes.getLength(); i++) {
			Node node = nodes.item(i);
			
			if (node.hasAttributes()) {
				result.add(getJob(node));
			}			
		}	
		
		return result;
	}
	
	/**
	 * loadParks() - loads the parks from an
	 * XML file for Urban Parks application.
	 * 
	 * 
	 * @return Collection<Park> that represents
	 * the list of parks in the application.
	 * @throws UPException 
	 */
	
	public Collection<Park> loadParks() throws UPException {
		
		Collection<Park> result = new ArrayList<Park>();
		
		Document document = getDocument(my_parks_xml);
		NodeList nodes = getNodeList(document, PARK_LIST);
		
		for(int i = 0; i < nodes.getLength(); i++) {
			Node node = nodes.item(i);
			
			if (node.hasAttributes()) {				
				
				result.add(getPark(node));
			}			
		}	
		
		return result;
	}
	
	/**
	 * loadConfig() - loads the configuration from an
	 * XML file for Urban Parks application.
	 * 
	 * 
	 * @return Collection<Integer> that represents
	 * the configuration of the application.
	 * @throws UPException 
	 */
	
	public int [] loadConfig() throws UPException {		

		int [] result = new int[2];
		int count = 0;
		
		Document document = getDocument(CONFIG_FILE);
		NodeList nodes = getNodeList(document, CONFIG_LIST);
		
		for(int i = 0; i < nodes.getLength(); i++) {
			Node node = nodes.item(i);
			
			if (node.hasAttributes()) {	
				
				result[count] = getConfig(node);
				
				count++;
			}			
		}	
		
		return result;	
	}
	
	/**
	 * saveUsers(Collection<User>) - saves the users to
	 * a XML file in the Urban Parks application.
	 * 
	 * @assumes the_users and all elements are non-null.
	 * @param the_users the Collection of User objects to save.
	 * @throws UPException 
	 */
	
	public void saveUsers(final Collection<User> the_users) throws UPException {
		
		Document document = getNewDocument();
						
		Element rootElement = document.createElement(USER_LIST);
		document.appendChild(rootElement);
		
		for(User user : the_users) {
			Element child = document.createElement(USER_NODE);
			child.setAttribute("FirstName", user.getFirstName());
			child.setAttribute("LastName", user.getLastName());
			child.setAttribute("Email", user.getEmail());
			child.setAttribute("Role", user.getRole().name());
			child.setAttribute("Comments", user.getComments());
			child.setAttribute("isBlackballed", new Boolean(user.isBlackballed()).toString());
			rootElement.appendChild(child);
			
			Element postal = document.createElement("PostalAddress");
			postal.setAttribute("Street", user.getPostalAddress().getStreet());
			postal.setAttribute("City", user.getPostalAddress().getCity());
			postal.setAttribute("State", user.getPostalAddress().getState());
			postal.setAttribute("PostalCode", user.getPostalAddress().getPostalCode());
			child.appendChild(postal);
			
			Element phone = document.createElement("PhoneNumber");
			phone.setAttribute("Digits", user.getPhoneNumber().getPhoneNumber());
			child.appendChild(phone);
			
		}			
			
			writeFile(document, my_users_xml);
		
	}
	
	/**
	 * saveJobs(Collection<Job>) - saves the jobs to
	 * a XML file in the Urban Parks application.
	 * 
	 * @assumes the_jobs and all elements are non-null.
	 * @param the_jobs the Collection of Job objects to save.
	 * @throws UPException 
	 */
	
	public void saveJobs(final Collection<Job> the_jobs) 
			throws UPException {
		
			Document document = getNewDocument();
			
			Element rootElement = document.createElement(JOB_LIST);
			document.appendChild(rootElement);
			
			for(Job job : the_jobs) {
				Element child = document.createElement(JOB_NODE);
				child.setAttribute("Name", job.getName());
				child.setAttribute("ParkId", Integer.toString(job.getParkId()));
				child.setAttribute("StartTime", getDateString(job.getStartTime()));
				child.setAttribute("Endtime", getDateString(job.getEndTime()));
				child.setAttribute("LightSize", Integer.toString(job.getTotalLightVolunteersNeeded()));
				child.setAttribute("MediumSize", Integer.toString(job.getTotalMedVolunteersNeeded()));
				child.setAttribute("HeavySize", Integer.toString(job.getTotalHeavyVolunteersNeeded()));
				child.setAttribute("Light", getStringIds(job.getLightVolunteerIDs()));
				child.setAttribute("Medium", getStringIds(job.getMediumVolunteerIDs()));
				child.setAttribute("Heavy", getStringIds(job.getHeavyVolunteerIDs()));
				rootElement.appendChild(child);
			}
			
			writeFile(document, my_jobs_xml);
		
	}
	
	/**
	 * saveParks(Collection<Park>) - saves the parks to
	 * a XML file in the Urban Parks application.
	 * 
	 * @assumes the_parks and all elements are non-null.
	 * @param the_parks the Collection of Park objects to save.
	 * @throws UPException 
	 */

	public void saveParks(final Collection<Park> the_parks) throws UPException {
		
		Document document = getNewDocument();
		
		Element rootElement = document.createElement(PARK_LIST);
		document.appendChild(rootElement);
		
		for (Park park : the_parks) {
			Element child = document.createElement(PARK_NODE);
			child.setAttribute("Name", park.getName());
			child.setAttribute("ManagerID", Integer.toString(park.getManagerID()));
			child.setAttribute("DistrictID", park.getDistrict());
			child.setAttribute("RegionID", park.getRegion());
			rootElement.appendChild(child);
			
			Element postal = document.createElement("PostalAddress");
			postal.setAttribute("Street", park.getAddress().getStreet());
			postal.setAttribute("City", park.getAddress().getCity());
			postal.setAttribute("State", park.getAddress().getState());
			postal.setAttribute("PostalCode", park.getAddress().getPostalCode());
			child.appendChild(postal);
			
			Element phone = document.createElement("PhoneNumber");
			phone.setAttribute("Digits", park.getPhoneNumber().getPhoneNumber());
			child.appendChild(phone);
			
		}
		
		writeFile(document, my_parks_xml);
	
	}
	
	/**
	 * Takes a GregorianCalendar object and returns the
	 * String representation.
	 * 
	 * @assumes the_time is non-null.
	 * @param the_time The date and time to be processed.
	 * @return String representation of the date parameter.
	 */
	protected String getDateString(final GregorianCalendar the_time) {
		String result = "";
		
		DateFormat dfm = new SimpleDateFormat("yyyy-MM-dd hh:mm aaa");
		
		result = dfm.format(the_time.getTime());
		
		return result;
		
	}
	
	/**
	 * Writes a new xml file to the XMLDom document.
	 * @assumes the_document and the_file non-null.
	 * @param the_document XMLDom
	 * @param the_file to write to.
	 * @throws UPException
	 */
	
	protected void writeFile(final Document the_document, final String the_file)
		throws UPException {
		
		TransformerFactory transformerFactory = TransformerFactory.newInstance();
		
		try {
			
			Transformer transformer = transformerFactory.newTransformer();
			DOMSource source = new DOMSource(the_document);
			StreamResult result = new StreamResult(new File(the_file));
			try {
				transformer.setOutputProperty(OutputKeys.INDENT, "yes");
				transformer.transform(source, result);					
			} catch (final TransformerException te) {
				throw new UPException("Write file failed: " + te.toString());
			}
			
		} catch (final TransformerConfigurationException tce) {
			throw new UPException("Write file failed: " + tce.toString());
		}
		
	}
	
	/**
	 * Returns a String representation of a list of Integers
	 * with comma separation.
	 * 
	 * @assumes list is non-null.
	 * @param list The list of integers.
	 * @return String representation of the list, comma separated.
	 */
	protected String getStringIds(final List<Integer> list) {
		
		String result = "";
		
		for (Integer x : list) {
			
			result += Integer.toString(x) + ",";			
		}		
				
		return result;
	}
	
	/**
	 * Creates a new XMLDom document.
	 * @return Document to write to.
	 * @throws UPException  when new document cannot be parsed.
	 */
	
	
	protected Document getNewDocument() throws UPException {
		
		DocumentBuilder docBuilder = null;
		Document document = null;
		
		try {
			docBuilder = my_factory.newDocumentBuilder();
			document = docBuilder.newDocument();
			
		} catch (ParserConfigurationException e1) {
			throw new UPException("Unable to parse file: " + e1.getMessage());
		}		
		
		return document;
		
	}
	
		
	/**
	 * getDocument() - gets the DOM object to parse.
	 * 
	 * @assumes the_filename is non-null.
	 * @param the_filename The String for the name of the file to retrieve.
	 * @return Document DOM object.
	 * @throws UPException When the file is not loaded correctly.
	 */
	
	protected Document getDocument(final String the_filename) throws UPException {
		
		DocumentBuilder builder = null;
		Document document = null;
		
		try {
			
			builder = my_factory.newDocumentBuilder();
			document = builder.parse(getFile(the_filename));			
			
			} catch(final ParserConfigurationException e) {
				throw new UPException("Loading program failed" + e.getMessage());
			} catch (final SAXException saxe) {
				throw new UPException("Loading program failed" + saxe.getMessage());
			} catch (final IOException ioe) {
				throw new UPException("Loading program failed" + ioe.getMessage());
			}
			
			return document;
	}
	
	/**
	 * getFile() - gets the application XML file stream.
	 * 
	 * @assumes the_filename is non-null.
	 * @param the_filename The name of the file to get.
	 * @return FileInputStream from the XML file.
	 * @throws UPException when unable to get file input stream.
	 */
	
	protected FileInputStream getFile(final String the_filename) 
			throws UPException {
		
		FileInputStream result = null;
		
		try {
			result = new FileInputStream(the_filename);
		} catch(final Exception e) {
			throw new UPException("File retrieval failed: " + e.getMessage());		
		}
		
		return result;
	}
	
	/**
	 * getNodeList(Document, String) - gets the child node list.
	 * 
	 * @assumes the_document and the_node are non-null.
	 * @param the_document DOM object.
	 * @param the_node name.
	 * @return NodeList of child nodes.
	 */
	
	protected NodeList getNodeList(final Document the_document,
			                       final String the_node) {
		NodeList result = null;
		
		Element root = the_document.getDocumentElement();		
		result = root.getChildNodes();
		
		return result;		
	}
	
	/**
	 * getuser(Node) - gets the user node.
	 * 
	 * @assumes the_node is non-null.
	 * @param the_node representing the User.
	 * @return User object.
	 */
	
	protected User getUser(final Node the_node) {
		
		User result = null;
				
		result = new User(getAttribute(the_node, "FirstName"),
				          getAttribute(the_node, "LastName"),
				          getAttribute(the_node, "Email"),
				          getPhoneNumber(the_node),
				          getRole(the_node, "Role"),
				          getAttribute(the_node, "Comments"),				         
				          getPostalAddress(the_node),
				          getBooleanAttribute(the_node,"isBlackballed"));
		
		return result;		
		
	}
	
	/**
	 * getJob(Node) - gets the Job elements.
	 * 
	 * @assumes the_node is non-null.
	 * @param the_node representing the Job.
	 * @return Job object.
	 * @throws UPException 
	 */
	
	protected Job getJob(final Node the_node) 
			throws UPException {
		
		Job result = null;
		
		result = new Job(getAttribute(the_node, "Name"),
						 getIntegerAttribute(the_node, "ParkId"),
				         getGregorianCalendar(the_node, "StartTime"),
					     getGregorianCalendar(the_node, "Endtime"),
				         getIntegerArrayAttribute(the_node, "Light", "LightSize"),
				         getIntegerArrayAttribute(the_node, "Medium", "MediumSize"),
				         getIntegerArrayAttribute(the_node, "Heavy", "HeavySize"));		                 
		
		return result;
	}
	
	/**
	 * getPark(Node) - gets the Park elements.
	 * 
	 * @assumes the_node is non-null.
	 * @param the_node representing the Park.
	 * @return Park object.
	 */
	
	protected Park getPark(final Node the_node) {
		
		Park result = null;
		
		result = new Park(getAttribute(the_node, "Name"),
				          getPostalAddress(the_node),
				          getPhoneNumber(the_node),
				          getIntegerAttribute(the_node, "ManagerID"),
				          getAttribute(the_node, "DistrictID"),
				          getAttribute(the_node, "RegionID"));

		return result;
	}
	
	/**
	 * getConfig(Node) - gets the config elements.
	 * 
	 * @assumes the_node is non-null.
	 * @param the_node representing the Config.
	 * @return int value.
	 */
	
	protected int getConfig(final Node the_node) {
		int result = 0;
		
		String temp = getAttribute(the_node, "Value");
		
		result = Integer.parseInt(temp);		
		
		return result;
	}
	
	/**
	 * getAttribute(Node, String) - gets the Job elements.
	 * 
	 * @assumes the_node and the_name are non-null
	 * @param the_node representing the Job.
	 * @return Job object.
	 */
	
	protected String getAttribute(final Node the_node,
			                      final String the_name) {
		
		String result = "";
		
		Element attributes = (Element) the_node;
		
		result = attributes.getAttribute(the_name);	
				
		return result;
		
	}
	
	/**
	 * getIntegerArraryAttribute(Node, String) - gets the integers.
	 * 
	 * @assumes the_node, the_name and the_size are all non-null.
	 * @param the_node to parse.
	 * @param the_name to look for.
	 * @param the_size fo the array to create.
	 * @return int [] array with users.
	 */
	
	protected int [] getIntegerArrayAttribute(final Node the_node,
			                                  final String the_name,
			                                  final String the_size) {
		
		
		/* Get and set the size of the array. */
		String total = getAttribute(the_node, the_size);		
		int size = Integer.parseInt(total);		
		
		/* Get user quantity signed up for the job. */
		String temp = getAttribute(the_node, the_name);		
		String [] arr = temp.split(",");
		
		/* Initialize array and add users. */				
		int [] result = new int[size];
		
		for (int i = 0; i < arr.length; i++) {
			
			if (!arr[i].isEmpty()) {
				result[i] = Integer.parseInt(arr[i]);
			}				
			
		}
		
		return result;
		
	}
	
		
	/**
	 * getIntegerAttribute(Node, string) - gets integer
	 * from XML file.
	 * 
	 * @assumes the_node and the_name are non-null.
	 * @param the_node to search.
	 * @param the_name to search with.
	 * @return integer representation of attribute.
	 */
	
	protected Integer getIntegerAttribute(final Node the_node,
            							  final String name) {
				
		return Integer.parseInt(getAttribute(the_node, name));	
	}
	
	/** 
	 * Gets a GregorianCalendar from the node.
	 * 
	 * @assumes that the_node and name are non-null.
	 * @param the_node to evaluate.
	 * @param name of the parent node.
	 * @return GregorianCalendar
	 * @throws UPException when unable to retrieve the GregorianCalendar.
	 */	

	protected GregorianCalendar getGregorianCalendar(final Node the_node,
			                                         final String name) 
			                                        		 throws UPException {
		
		GregorianCalendar result = new GregorianCalendar();
		Date dt = getDateAttribute(the_node, name);
		
		result.setTime(dt);
		
		return result;
	}
	
	/**
	 * getDateAttribute(Node, string) - gets Date
	 * from XML file.
	 * 
	 * @assumes the_node and the_name are non-null.
	 * @param the_node to search.
	 * @param the_name to search with.
	 * @return Date representation of attribute.
	 * @throws UPException when unable to retrieve the date attribute.
	 */
	
	protected Date getDateAttribute(final Node the_node,
            						final String the_name) throws UPException {
        
		Date result = null;
		DateFormat dfm = new SimpleDateFormat("yyyy-MM-dd hh:mm aaa");
		
		try {
			result = dfm.parse(getAttribute(the_node, the_name));
		} catch (final ParseException pe) {
			throw new UPException("Invalid date in XML: " + pe.getMessage());
		}
        
		return result;
	}
	
	
	/**
	 * getBooleanAttribute(Node, string) - gets boolean
	 * from XML file.
	 * 
	 * @assumes the_node and the_name are non-null.
	 * @param the_node to search.
	 * @param name to search with.
	 * @return boolean representation of attribute.
	 */
	
	protected boolean getBooleanAttribute(final Node the_node,
                  					      final String the_name) {
				
		return Boolean.parseBoolean(getAttribute(the_node, the_name));	
	}
	
	/**
	 * getRole(Node, String) - gets the User role.
	 * 
	 * @assumes the_node and the_string are non-null.
	 * @param the_node to search.
	 * @param the_name to search for.
	 * @return Role representation of User.
	 */	
	
	protected Role getRole(final Node the_node,
			               final String the_name) {
		
		Role result = null;
		
		Element attributes = (Element) the_node;
		
		String role = attributes.getAttribute(the_name);
		
		if (role.equalsIgnoreCase("Volunteer")) {
			result = Role.VOLUNTEER;
		} else if (role.equalsIgnoreCase("ParkManager")) {
			result = Role.PARKMANAGER;
		} else if (role.equalsIgnoreCase("StaffMember")) {
			result = Role.STAFFMEMBER;
		}
		
		return result;
	}
	
	/**
	 * Gets a PostalAddress from the parent node.
	 * 
	 * @assumes Node is non-null.
	 * @param the_node of the parent.
	 * @return PostalAddress.
	 */
	
	
	protected PostalAddress getPostalAddress(Node the_node) {
		
		PostalAddress result = null;
		
		if (the_node.hasChildNodes()) {
			
			NodeList nodes = the_node.getChildNodes();
			
			for(int i = 0; i < nodes.getLength(); i++) {
				
				Node node = nodes.item(i);
				
				if ((node.hasAttributes()) &&
					(node.getNodeName().equals("PostalAddress"))) {
					
					result = new PostalAddress(getAttribute(node, "Street"),
							                   getAttribute(node, "City"),
							                   getAttribute(node, "State"),
							                   getAttribute(node, "PostalCode"));
					
					
				}			
			}		
			
		}
		
		return result;
		
	}
	
	/**
	 * getPhoneNumber from parent node.
	 * 
	 * @assumes Node is non-null.
	 * @param the_node of the parent.
	 * @return PhoneNumber.
	 */
	
	protected PhoneNumber getPhoneNumber(Node the_node) {
		
		PhoneNumber result = null;
		
		if (the_node.hasChildNodes()) {
			
			NodeList nodes = the_node.getChildNodes();
			
			for(int i = 0; i < nodes.getLength(); i++) {
				
				Node node = nodes.item(i);
				
				if ((node.hasAttributes()) &&
					(node.getNodeName().equals("PhoneNumber"))) {
					
					result = new PhoneNumber(getAttribute(node, "Digits"));							                  
					
				}			
			}		
			
		}
		
		return result;
		
	}

}

